import os

import gridfs
from pymongo import MongoClient, UpdateOne


class MongoUtil:
    def __init__(self, host='localhost', port=27017, db_name='test', collection_name='data'):
        self.client = MongoClient(host, port)
        self.db = self.client[db_name]
        self.collection = self.db[collection_name]
        self.fs = gridfs.GridFS(self.db)

    def insert_one(self, documents, bypass_document_validation=False, session=None, comment=None):
        """
        如果主键重复 会报错
        Args:
            documents:  {"_id":"",...}
            bypass_document_validation: (这个不建议动): 如下
            session：在 client.start_session() 块内，所有操作都会共享同一个会话上下文。
            comment：在插入操作中添加了一个注释 "Inserting test document"，这有助于在日志或监控工具中标识该操作。

        Returns:

        """
        result = self.collection.insert_one(documents,
                                            bypass_document_validation=bypass_document_validation,
                                            session=session, comment=comment)
        # InsertOneResult('test2', acknowledged=True)
        return result.inserted_id

    def insert_many(self, list_documents, ordered=True, bypass_document_validation=False, session=None, comment=None):
        """
        Args:
            list_documents:  [{"_id":"",...},{}]
            ordered:如果为 True（默认值），文档将按照提供的顺序依次在服务器上进行插入。如果发生错误，
            所有剩余的插入操作将被中止。如果为 False，文档将以任意顺序在服务器上进行插入，可能会并行进行，
            并且将尝试所有文档的插入操作。但依然会报错,不过会将后面的也插入进去。
            bypass_document_validation: (这个不建议动)（可选）如果为 True，允许跳过文档级别的验证。默认值为 False。
            文档级别的验证指的是在插入或更新文档时，MongoDB 会根据集合的验证规则（schema validation rules）
            来检查文档的结构和内容是否符合预期。如果文档不符合这些规则，插入或更新操作会失败。
            bypass_document_validation 选项允许你在执行写操作时跳过这些验证规则。这意味着即使文档不符合集合的验证规则，
            插入或更新操作也会成功。使用这个选项可以绕过数据验证，从而允许插入或更新任何结构的文档。

            session：在 client.start_session() 块内，所有操作都会共享同一个会话上下文。
            comment：在插入操作中添加了一个注释 "Inserting test document"，这有助于在日志或监控工具中标识该操作。


        Returns: 返回的是一个lists [_id1,_id2,...]

        """
        result = self.collection.insert_many(list_documents, ordered=ordered,
                                             bypass_document_validation=bypass_document_validation,
                                             session=session,
                                             comment=comment)
        return result.inserted_ids  # 返回的是一个lists

    def replace_one(self, filter, replacement, upsert=False, bypass_document_validation=False, collation=None,
                    hint=None, session=None, let=None, comment=None):
        """


        Args:
            filter:
            replacement:
            upsert:  upsert 选项可以在不存在匹配文档时插入一个新文档
            bypass_document_validation: (这个不建议动) 如上
            collation: Collation 是 MongoDB 中用于定义字符串比较规则和排序顺序的工具。 一般不完全清楚规则不建议使用
            hint: 一个索引，指定用于支持查询谓词的索引， hint="name_index" 或 hint=[("field", pymongo.ASCENDING)]
            let:  一个字典，这些变量可以在 replacement 文档中使用 $$var
            session：在 client.start_session() 块内，所有操作都会共享同一个会话上下文。
            comment：在插入操作中添加了一个注释 "Inserting test document"，这有助于在日志或监控工具中标识该操作。

        Returns:

        """
        result = self.collection.replace_one(filter,
                                             replacement,
                                             upsert=upsert,
                                             bypass_document_validation=bypass_document_validation,
                                             collation=collation,
                                             hint=hint,
                                             session=session,
                                             let=let,
                                             comment=comment)
        # result: UpdateResult({'n': 0, 'nModified': 0, 'ok': 1.0, 'updatedExisting': False}, acknowledged=True)
        """
        n：匹配到的文档数量。在这个例子中，n: 0 表示没有找到任何符合条件的文档。
        nModified：实际被修改的文档数量。在这个例子中，nModified: 0 表示没有任何文档被修改。这可能是因为没有找到符合条件的文档（与 n 的值一致），或者找到的文档内容已经与更新后的内容相同，不需要修改。
        ok：操作的状态码，1.0 表示操作成功。
        updatedExisting：布尔值，指示是否更新了现有文档。在这个例子中，updatedExisting: False 表示没有更新现有的文档。这通常与 n: 0 一致，即没有找到任何文档进行更新。
        acknowledged：布尔值，指示操作是否被确认。在这个例子中，acknowledged: True 表示操作已经被服务器确认。这是由 pymongo 提供的附加信息，用于确认操作是否成功传输到服务器并被处理。
        """
        # 将 UpdateResult 对象转换为字典
        result_dict = {
            'matched_count': result.matched_count,
            'modified_count': result.modified_count,
            'acknowledged': result.acknowledged,
            "raw_result": result.raw_result,  # {'n': 1, 'nModified': 0, 'ok': 1.0, 'updatedExisting': True}
            'upserted_id': result.upserted_id,
            # upserted_id 字段的值取决于 upsert 选项的使用情况。如果 upsert=True 且插入了一个新文档，则 upserted_id 会包含新插入文档的 _id。如果只是更新了现有文档，则 upserted_id 会为 None。
            'updated_existing': result.upserted_id is None and result.matched_count > 0
        }

        return result_dict

    def bulk_write(self, documents, filter_key="_id", ordered=True,
                   bypass_document_validation=False,
                   session=None,
                   comment=None,
                   let=None):

        operations = []
        for doc in documents:
            filter = {}
            if isinstance(filter_key, list):
                for v_k in filter_key:
                    filter[v_k] = doc[v_k]
            elif isinstance(filter_key, str):
                filter = {filter_key: doc[filter_key]}
            else:
                raise Exception("mongodb filter_key 类型错误")

            update = {"$set": doc}
            # 启用 upsert 不存在就插入
            operations.append(UpdateOne(filter, update, upsert=True))

        result = self.collection.bulk_write(operations,
                                            ordered=ordered,
                                            bypass_document_validation=bypass_document_validation,
                                            session=session,
                                            comment=comment,
                                            let=let)
        return result

    async def find(self, _filter: dict, projection: dict = None, sort_by: dict = None,
                   skip: int = 0, limit: int = 0) -> list:
        """
        Args:
            _filter: 查询条件
            projection: 指定返回的字段
            sort_by: 排序
            skip: 跳过的数据量
            limit: 返回的数量

        Returns:
        """

        cursor = self.collection.find(filter=_filter,
                                      projection=projection,
                                      sort=sort_by,
                                      skip=skip,
                                      limit=limit)

        results = []
        for document in cursor:
            results.append(document)
        return results


    def update(self, query, new_values):
        try:
            self.collection.update_many(query, {"$set": new_values})
            print("Update successful")
        except Exception as e:
            print("Update failed:", e)

    def delete(self, query):
        try:
            self.collection.delete_many(query)
            print("Deletion successful")
        except Exception as e:
            print("Deletion failed:", e)

    def gridfs_up_file(self, file_path, file_name=None, _id=None, content_type=None, metadata=None, encoding='utf-8',
                       chunk_size=255 * 1024):
        """
        Args:
            file_path:可以是 :class:`bytes` 实例或提供 :meth:`read` 方法的类似文件的对象。如果传递了 `encoding` 关键字参数，`data` 也可以是 :class:`str` 实例，在写入之前会按照 `encoding` 进行编码
            file_name: 文件名
            _id:  唯一 id
            content_type: 文件类型
            metadata: 元数据
            encoding: 编码 传字符串时需要
            chunk_size: 每个块大小

        Returns:
        """
        file_data = file_path
        if os.path.isfile(file_path):
            file_data = open(file_path, 'rb')
            file_name = file_name or file_path.split(os.sep)[-1]

        file_id = self.fs.put(data=file_data,
                              filename=file_name,
                              _id=_id,
                              content_type=content_type,
                              metadata=metadata,
                              encoding=encoding,
                              chunk_size=chunk_size)  # 默认 255kb 可以指定每个块的大小 建议大小 8M ,mongodb会按照这个大小切分文件
        if os.path.isfile(file_path):
            file_data.close()
        return file_id

    def gridfs_get_file(self, file_id, session=None, encoding='utf-8'):
        # gridfs.grid_file.GridOut
        result = self.fs.get(file_id=file_id, session=session)
        file_data = result.read().decode(encoding)
        data = {
            "_id": result._id,  # 主id
            "filename": result.filename,
            "data": file_data,  # 数据 如果为json 需要自己转换
            "content_type": result.content_type,
            "metadata": result.metadata
        }

        return data
